到目前为止,本章已经描述了几种用位对数字进行编码的方案,但没有提到这些值在内存中是如何组织的。对于现代系统,内存的最小可寻址单元是字节,它由八位组成。因此,要存储从地址 X 开始的一字节值(例如,char
类型的变量),您实际上没有任何选择 —— 只需将字节存储在位置 X 处。
然而,对于多字节值(例如,short
或int
类型的变量),硬件有更多选项用于将值的字节分配给内存地址。例如,考虑一个两字节短变量s
,其字节标记为 A(包含s
的高位)和 B(包含s
的低位)。当系统被要求在地址 X(即地址 X 和 X+1)存储像s
这样的short
时,它必须定义变量(A 或 B)的哪个字节应占用哪个地址(X 或 X+1)。 图 1 显示了在内存中存储s
的两个选项。
图 1. 从内存地址 X 开始的两字节短整型的两种可能的内存布局
系统的字节顺序(或字节序)定义其硬件如何将多字节变量的字节分配给连续的内存地址。尽管对于仅在单个系统上运行的程序来说,字节顺序很少是一个问题,但如果您的某个程序尝试一次打印一个字节或者您正在使用调试器检查变量,则可能会显得令人惊讶。
例如,考虑以下程序:
#include <stdio.h>
int main(int argc, char **argv) {
// Initialize a four-byte integer with easily distinguishable byte values
int value = 0xAABBCCDD;
// Initialize a character pointer to the address of the integer.
char *p = (char *) &value;
// For each byte in the integer, print its memory address and value.
int i;
for (i = 0; i < sizeof(value); i++) {
printf("Address: %p, Value: %02hhX\n", p, *p);
p += 1;
}
return 0;
}
该程序分配一个四字节整数,并按照从最高有效到最低有效的顺序将字节初始化为十六进制值0xAA
、0xBB
、0xCC
和0xDD
。然后,它从整数的基地址开始一次打印一个字节。如果您期望字节按字母顺序打印,这是情有可原的。然而,常用的 CPU 架构(即 x86 和大多数 ARM 硬件)在执行示例程序时以相反的顺序打印字节:
$ ./a.out
Address: 0x7ffc0a234928, Value: DD
Address: 0x7ffc0a234929, Value: CC
Address: 0x7ffc0a23492a, Value: BB
Address: 0x7ffc0a23492b, Value: AA
x86 CPU 以 little-endian 格式存储整数——从连续地址中的最低有效字节(小端
)到最高有效字节。其他 big-endian CPU 架构以相反的顺序存储多字节整数。图图 2 描绘了 (a) 大端和 (b) 小端布局中的四字节整数。
图 2. (a) 大端格式和 (b) 小端格式的四字节整数的内存布局
看似奇怪的“字节序”术语源自乔纳森·斯威夫特的讽刺小说 格列佛游记 (1726)1。在故事中,格列佛发现自己身处两个六英寸高的帝国之中,他们正在为打破鸡蛋的正确方法而进行一场战争。不来夫斯库的“大端”帝国破解了鸡蛋的大端,而小人国的“小端”帝国的人们破解了小端。
在计算世界中,系统是 big-endian 还是 little-endian 通常只影响跨机器通信(例如通过网络)的程序。在系统之间通信数据时,两个系统必须就字节顺序达成一致,以便接收者正确解释该值。 1980 年,丹尼·科恩 (Danny Cohen) 向互联网工程任务组 (IETF) 撰写了一篇题为“关于圣战与和平诉求”的说明2。在该说明中,科恩采用了 Swift 的“endian”术语,并建议 IETF 采用标准字节顺序进行网络传输。 IETF 最终采用 big-endian 作为“网络字节顺序”标准。
C 语言提供了两个库,允许程序出于通信目的对整数的字节3,4 重新排序。
4.7.1. 引用
- Jonathan Swift. Gulliver’s Travels. http://www.gutenberg.org/ebooks/829
- Danny Cohen. On Holy Wars and a Plea for Peace. https://www.ietf.org/rfc/ien/ien137.txt
- https://linux.die.net/man/3/byteorder
- https://linux.die.net/man/3/endian